/*
* Copyright (c) 1986-2015, Serkan OZAL, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tr.com.serkanozal.jillegal.agent;
import java.io.File;
import java.io.PrintStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import sun.management.VMManagement;
import tr.com.serkanozal.jillegal.agent.JillegalAgentClassTransformer.ClassDataProcessorHandler;
import tr.com.serkanozal.jillegal.agent.util.ClassLoaderUtil;
import tr.com.serkanozal.jillegal.agent.util.InstanceUtil;
import tr.com.serkanozal.jillegal.agent.util.LogUtil;
import tr.com.serkanozal.jillegal.agent.util.OsUtil;
import com.sun.tools.attach.VirtualMachine;
/**
* Java agent implementation for <tt>jillegal-agent</tt> framework.
*
* @author Serkan OZAL
*/
@SuppressWarnings("restriction")
public final class JillegalAgent {
public static String VERSION = "2.0";
final static public String INSTR_JAR_PREFIX = "jillegal-agent";
final static public String NATIVE_METHOD_PREFIX = "jillegal_agent";
private static volatile Instrumentation inst;
private static volatile boolean initialized;
static {
JillegalAgentClassTransformer.init();
}
private JillegalAgent() {
}
public static void agentmain(String arguments, Instrumentation i) {
initAtMain(arguments, i);
LogUtil.debug("Agentmain: " + inst + " - " + "Arguments: " + arguments);
}
public static void premain(String arguments, Instrumentation i) {
initAtMain(arguments, i);
LogUtil.debug("Premain: " + inst + " - " + "Arguments: " + arguments);
}
private static String getClassPath() {
ClassLoader classLoader = ClassLoaderUtil.getClassLoader();
StringBuilder classPathBuilder = new StringBuilder();
classPathBuilder.
append(System.getProperty("java.class.path")).
append(File.pathSeparator);
if (System.getProperty("surefire.test.class.path") != null) {
classPathBuilder.
append(System.getProperty("surefire.test.class.path")).
append(File.pathSeparator);
}
if (classLoader instanceof URLClassLoader) {
URLClassLoader urlClassLoader = (URLClassLoader)classLoader;
URL[] urls = urlClassLoader.getURLs();
for (URL u : urls) {
String filePath = u.getFile();
if (OsUtil.isWindows() && filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
classPathBuilder.
append(filePath).
append(File.pathSeparator);
}
}
return classPathBuilder.toString();
}
private static void initAtMain(String arguments, Instrumentation i) {
try {
inst = i;
processArguments(arguments, i);
JarFile agentJarFile = null;
final StringTokenizer st = new StringTokenizer(getClassPath(), File.pathSeparator);
while (st.hasMoreTokens()) {
String classpathEntry = st.nextToken().trim();
File f = new File(classpathEntry);
if (f.exists() && f.getName().startsWith(INSTR_JAR_PREFIX)) {
agentJarFile = new JarFile(classpathEntry);
break;
}
}
LogUtil.debug("Agent Jar File: " + agentJarFile.getName());
if (agentJarFile != null) {
inst.appendToBootstrapClassLoaderSearch(agentJarFile);
}
if (agentJarFile != null) {
inst.appendToSystemClassLoaderSearch(agentJarFile);
}
initialized = true;
Class<?> clazz =
ClassLoader.getSystemClassLoader().loadClass(JillegalAgent.class.getName());
Field instField = clazz.getDeclaredField("inst");
instField.setAccessible(true);
instField.set(null, inst);
Field initializedField = clazz.getDeclaredField("initialized");
initializedField.setAccessible(true);
initializedField.set(null, initialized);
}
catch (Throwable t) {
LogUtil.error("Error at JillegalAgent.initAtMain()", t);
}
}
private synchronized static void initInstrumentationIfNeeded() {
if (inst == null) {
try {
Class<?> clazz =
ClassLoader.getSystemClassLoader().loadClass(JillegalAgent.class.getName());
Field instField = clazz.getDeclaredField("inst");
instField.setAccessible(true);
inst = (Instrumentation) instField.get(null);
}
catch (Throwable t) {
LogUtil.error("Error at JillegalAgent.initInstrumentationIfNeeded()", t);
}
}
}
public static Instrumentation getInstrumentation() {
initInstrumentationIfNeeded();
return inst;
}
public static void init() {
initInternal(null);
}
public static void init(String arguments) {
initInternal(arguments);
}
private synchronized static void initInternal(String arguments) {
if (initialized) {
LogUtil.warn("Agent has been already initialized");
return;
}
try {
LogUtil.intro();
loadAgent(arguments);
}
catch (Throwable t) {
LogUtil.error("Error at JillegalAgent.initInternal(String arguments)", t);
throw new RuntimeException(t);
}
}
private static void loadAgent(String arguments) throws Exception {
VirtualMachine vm = VirtualMachine.attach(getPidFromRuntimeMBean());
String agentPath = null;
String classPath = getClassPath();
LogUtil.info("OS Name: " + OsUtil.OS);
LogUtil.info("Class Path: " + classPath);
for (String entry : classPath.split(File.pathSeparator)) {
File f = new File(entry);
if (f.exists() && f.getName().startsWith(INSTR_JAR_PREFIX)) {
agentPath = entry;
break;
}
}
LogUtil.info("Agent path: " + agentPath);
if (agentPath == null) {
throw new RuntimeException("Profiler agent is not in classpath ...");
}
if (arguments != null) {
vm.loadAgent(agentPath, arguments);
}
else {
vm.loadAgent(agentPath);
}
vm.detach();
}
public static void redefineClass(Class<?> clazz, byte[] byteCodes) {
initInstrumentationIfNeeded();
try {
inst.redefineClasses(new ClassDefinition(clazz, byteCodes));
}
catch (UnmodifiableClassException e) {
LogUtil.error("Error at JillegalAgent.redefineClass(Class<?> clazz, byte[] byteCodes)", e);
}
catch (ClassNotFoundException e) {
LogUtil.error("Error at JillegalAgent.redefineClass(Class<?> clazz, byte[] byteCodes)", e);
}
}
public static void retransformClass(Class<?> clazz) {
initInstrumentationIfNeeded();
try {
inst.retransformClasses(clazz);
}
catch (UnmodifiableClassException e) {
LogUtil.error("Error at JillegalAgent.retransformClass(Class<?> clazz)", e);
}
}
public static long sizeOf(Object obj) {
initInstrumentationIfNeeded();
if (obj == null) {
return 0;
}
else {
return inst.getObjectSize(obj);
}
}
public static boolean isInitialized() {
return initialized;
}
private static String getPidFromRuntimeMBean() throws Exception {
RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
Field jvmField = mxbean.getClass().getDeclaredField("jvm");
jvmField.setAccessible(true);
VMManagement management = (VMManagement) jvmField.get(mxbean);
Method method = management.getClass().getDeclaredMethod("getProcessId");
method.setAccessible(true);
Integer processId = (Integer) method.invoke(management);
return processId.toString();
}
private static void processArguments(String arguments, Instrumentation i) throws Exception {
if (arguments == null) {
return;
}
arguments = arguments.trim();
if (arguments.length() == 0) {
return;
}
String args[] = arguments.split("\\s+");
String command = args[0];
if ("-h".equals(command) || "-help".equals(command)) {
printUsage(false);
} else if ("-p".equals(command)) {
handleClassDataProcessorArguments(args, i);
} else {
invalidUsage();
throw new InvalidParameterException();
}
}
private static void handleClassDataProcessorArguments(String args[], Instrumentation i) throws Exception {
Collection<ClassDataProcessorHandler> handlers = createHandlersFromArguments(args);
if (handlers != null && !handlers.isEmpty()) {
JillegalAgentClassTransformer transformer = new JillegalAgentClassTransformer(handlers);
inst.addTransformer(transformer);
inst.setNativeMethodPrefix(transformer, NATIVE_METHOD_PREFIX);
}
}
private static Collection<ClassDataProcessorHandler> createHandlersFromArguments(String args[])
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if (args.length < 2) {
invalidUsage();
throw new InvalidParameterException();
}
List<ClassDataProcessorHandler> handlers = new ArrayList<ClassDataProcessorHandler>();
for (int i = 1; i < args.length; i++) {
String argParts[] = args[i].split("=");
if (argParts.length == 1) {
String classDataProcessorName = argParts[0];
ClassDataProcessor processor = InstanceUtil.createInstance(classDataProcessorName);
if (processor instanceof TargetAwareClassDataProcessor) {
handlers.add(
JillegalAgentClassTransformer.createHandlerAsTargetAware(
(TargetAwareClassDataProcessor) processor));
} else {
handlers.add(JillegalAgentClassTransformer.createHandlerAsInterestedInWithAll(processor));
}
} else if (argParts.length == 2) {
String classDataProcessorName = argParts[0];
ClassDataProcessor processor = InstanceUtil.createInstance(classDataProcessorName);
if (processor instanceof TargetAwareClassDataProcessor) {
handlers.add(
JillegalAgentClassTransformer.createHandlerAsTargetAware(
(TargetAwareClassDataProcessor) processor));
}
String targets[] = argParts[1].split(",");
for (String target : targets) {
handlers.add(JillegalAgentClassTransformer.createHandlerAsDefined(processor, target));
}
} else {
invalidUsage();
throw new InvalidParameterException();
}
}
return handlers;
}
private static void invalidUsage() {
System.err.println("Invalid usage!");
printUsage(true);
}
private static void printUsage(boolean error) {
@SuppressWarnings("resource")
PrintStream ps = error ? System.err : System.out;
ps.println(
"-javaagent:<path_to_agent_jar>/jillegal-agent-" + VERSION + ".jar" +
"[" +
"=" +
"-p " +
"[" +
"<class_data_processor>[=<target>[,<target>]*]" +
"]+" +
"]");
}
}